﻿/*
 * Ext Gantt 1.2
 * Copyright(c) 2009-2011 Mats Bryntse Consulting
 * mats@ext-scheduler.com
 * http://www.ext-scheduler.com/license.html
 *
 */

Ext.ns('Sch.gantt');

/**
 * @class Sch.gantt.DragDrop
 * @extends Ext.util.Observable
 * Internal plugin enabling drag and drop for tasks
 * @constructor
 * @param {Object} config The object containing the configuration of this model.
 */
Sch.gantt.DragDrop = function(gantt, config) {
    Ext.apply(this, config);
    
    // private
    this.gantt = gantt;
    this.ddGroup = gantt.id;

    gantt.on({
        afterrender : this.onRender, 
        beforedestroy : this.cleanUp,
        scope : this
    });
        
    Sch.gantt.DragDrop.superclass.constructor.call(this);
};

Ext.extend(Sch.gantt.DragDrop, Ext.util.Observable, {
    /**
      * @cfg useTooltip {Boolean} false to not show a tooltip while dragging
      */
    useTooltip : true,
    
    /**
     * An empty function by default, but provided so that you can perform custom validation on 
     * the item being dragged. This function is called during the drag and drop process and also after the drop is made
     * @param {Ext.data.Record} record The record being dragged
     * @param {Date} date The date corresponding to the current start date
     * @param {Int} duration The duration of the item being dragged, in minutes
     * @param {Ext.EventObject} e The event object
     * @return {Boolean} true if the drop position is valid, else false to prevent a drop
     */
    validatorFn : function(record, date, duration, e) {
        return true;
    },
    
    /**
     * @cfg {Object} validatorFnScope
     * The scope for the validatorFn
     */
    validatorFnScope : null,
    
    cleanUp : function() {
        this.gantt.dragZone.destroy();

        if (this.tip) {
            this.tip.destroy();
        }
    },
    
    // private
    onRender : function() {
        this.setupDragZone();
    },
    
    setupDragZone : function() {
        var me = this,
            g = this.gantt,
            v = g.getView();
            
        if (this.useTooltip) {
            this.tip = new Sch.gantt.Tooltip({}, g);
        }
        
        // The drag zone behaviour
        g.dragZone = new Sch.gantt.DragZone(v.mainBody, {
            ddGroup : this.ddGroup,
            validatorFn : this.validatorFn,
            validatorFnScope : this.validatorFnScope,
            gantt : g,
            tip : this.tip
        });
    }
});


/**
 * @ignore
 * @class Sch.DragProxy
 * @extends Ext.dd.StatusProxy
 * A status proxy showing only a copy of the item being dragged. No status indicator etc.
 * @constructor
 * @param {Object} config The object containing the configuration of this model.
 */
Sch.DragProxy = Ext.extend(Ext.dd.StatusProxy, {
    constructor : function(config){
        Ext.apply(this, config);
        this.id = this.id || Ext.id();
        this.el = new Ext.Layer({
            dh: {
                id: this.id, tag: "div", cls: "sch-gantt-dragproxy x-dd-drag-proxy", children: [
                    {tag: "div", cls: "x-dd-drag-ghost"}
                ]
            }, 
            shadow: false
        });
        this.ghost = Ext.get(this.el.dom.childNodes[0]);
        this.dropStatus = this.dropNotAllowed;
    },
    
    // Overridden to prevent the proxy from resetting itself and resetting the class property
    reset : function(clearGhost){
        this.el.dom.className = "sch-gantt-dragproxy x-dd-drag-proxy " + this.dropNotAllowed;
        this.dropStatus = this.dropNotAllowed;
        if(clearGhost){
            this.ghost.update("");
        }
    }
});



/**
 * @ignore
 * @class Sch.gantt.DragZone
 * @extends Ext.dd.DragZone
 * Custom dragzone that also acts as the dropzone, and constrains the drag to the table row that contains the dragged element
 * @constructor
 * @param {Object} config The object containing the configuration of this model.
 */
Sch.gantt.DragZone = Ext.extend(Ext.dd.DragZone, {
    containerScroll : false,
    getItemDepth : 8,
    
    constructor : function(el, config){
        config.proxy = new Sch.DragProxy({
            shadow : false,
            dropAllowed : Ext.dd.StatusProxy.prototype.dropAllowed,
            dropNotAllowed : Ext.dd.StatusProxy.prototype.dropNotAllowed
        });
        Sch.gantt.DragZone.superclass.constructor.apply(this, arguments);
        this.scroll = false;
        this.isTarget = true;
        this.ignoreSelf = false;

        // Stop task drag and drop when a resize handle, a terminal or a parent task is clicked
        this.addInvalidHandleClass('x-resizable-handle');
        this.addInvalidHandleClass('sch-gantt-terminal');
        this.addInvalidHandleClass('sch-gantt-progressbar-handle');

        Ext.dd.ScrollManager.register(this.gantt.getView().scroller);
    },

    destroy : function(){
        Sch.gantt.DragZone.superclass.destroy.call(this);
        Ext.dd.ScrollManager.unregister(this.gantt.getView().scroller);
    },
    
    autoOffset: function(x, y) {
        var xy = this.dragData.repairXY, // Original position of the element
            xDelta = x - xy[0],
            yDelta = y - xy[1];
        
        this.setDelta(xDelta, yDelta);
    },
    
    constrainTo : function(constrainTo){
        var ce = Ext.get(constrainTo),
            xy = ce.getXY();

        this.resetConstraints();
        this.initPageY = xy[1];

        this.setYConstraint(0, 0, this.yTickSize);
    },

    onDragOver: function(e, id){
                
        var g = this.gantt,
            x = this.proxy.el.getX() + g.getXOffset(this.dragData.record), // Adjust x position for certain task types
            newStart = g.getDateFromX(x, 'floor'),
            data = this.dragData;
               
        if (!data.hidden) {
            Ext.fly(data.sourceNode).hide();
            data.hidden = true;
        }
                
        if (!newStart) return;
                
        data.start = newStart;
        this.valid = this.validatorFn.call(this.validatorFnScope || g, 
                                            data.record, 
                                            newStart, 
                                            data.duration, 
                                            e) !== false;
        if (this.tip) {
            var end = Sch.util.Date.add(newStart, Sch.util.Date.MINUTE, data.duration);
            this.tip.update(newStart, end, this.valid);
        }
    },
            
    onStartDrag : function () {
        var rec = this.dragData.record;

        if (this.tip) {
            var start = rec.get('StartDate'),
                end = rec.get('EndDate');
            this.tip.show(Ext.get(this.dragData.sourceNode));
            this.tip.update(start, end, true);
        }
               
                
        this.gantt.fireEvent('dndstart', this.gantt, rec);
    },
            
    // On receipt of a mousedown event, see if it is within a draggable element.
    // Return a drag data object if so. The data object can contain arbitrary application
    // data, but it should also contain a DOM element in the ddel property to provide
    // a proxy to drag.
    getDragData: function(e) {
        var g = this.gantt,
            sourceNode = e.getTarget(g.eventSelector, this.getItemDepth);
                
        if (sourceNode) {
            var sourceNodeEl = Ext.get(sourceNode),
                v = g.getView(),
                taskEl = sourceNodeEl.is(g.eventSelector) ? sourceNode : sourceNodeEl.up(g.eventSelector).dom,
                sourceTaskRecord = g.resolveTask(taskEl.id);
                     
            if (g.fireEvent('beforednd', g, sourceTaskRecord, e) === false) {
                return null;
            }
                    
            var copy = sourceNode.cloneNode(true),
                increment = v.getSnapPixelAmount(),
                origXY = Ext.fly(sourceNode).getXY();
            copy.id = Ext.id();
            
            if (increment <= 1) {
                Ext.fly(copy).setStyle('left', 0);  // Reset any offset applied through CSS
            }
            
            this.setXConstraint(0, this.el.getWidth(), increment > 1 ? increment : 0);
            this.constrainTo(sourceNodeEl.up('div.x-grid3-row'));

            return {
                sourceNode : sourceNode,
                repairXY: origXY,
                ddel: copy,
                record : sourceTaskRecord,
                duration : Sch.util.Date.getDurationInMinutes(sourceTaskRecord.get('StartDate'), sourceTaskRecord.get('EndDate'))
            };
        }
        return null;
    },
            
    // Override, get rid of weird highlight fx in default implementation
    afterRepair : function(){
        Ext.fly(this.dragData.sourceNode).show();
        if (this.tip) {
            this.tip.hide();
        }
        this.dragging = false;
    },

    // Provide coordinates for the proxy to slide back to on failed drag.
    // This is the original XY coordinates of the draggable element.
    getRepairXY: function() {
        this.gantt.fireEvent('afterdnd', this.gantt);
        return this.dragData.repairXY;
    },
            
    onDragDrop: function(e, id){
        var target = this.cachedTarget || Ext.dd.DragDropMgr.getDDById(id),
            data = this.dragData,
            g = this.gantt,
            start = data.start,
            modified = false;
                
        if (start) {
            var end = Sch.util.Date.add(start, Date.MINUTE, data.duration),
                valid = false;
                    
            if (start && this.valid) {
                var r = data.record;

                modified = (r.get('StartDate') - start) !== 0 || (r.get('EndDate') - end) !== 0;

                r.beginEdit();
                r.set('StartDate', start);
                r.set('EndDate', end);
                r.endEdit();
                valid = true;
                        
                g.fireEvent('drop', g, r);
            }
        }
                
        if (this.tip) {
            this.tip.hide();
        }
                    
        g.fireEvent('afterdnd', g);

        if(valid && modified){ // valid drop?
            this.onValidDrop(target, e, id);
        }else{
            this.onInvalidDrop(target, e, id);
        }
    }
});

